<!DOCTYPE html>
<meta charset="utf-8">
<style>

table {
  width: 960px;
  border-spacing: 0;
  border-collapse: collapse;
}

th, td {
  padding: 4px;
}

th {
  text-align: left;
}

td {
  border: solid 1px #ccc;
  text-align: right;
}

td.fail {
  background: lightcoral;
}

td.success {
  background: lightgreen;
}

</style>
<table>
  <thead>
    <th>start</th>
    <th>end</th>
    <th colspan=5>actual intermediate values</th>
    <th>exp.</th>
    <th>act.</th>
  </thead>
  <tbody>
  </tbody>
</table>
<script src="../../d3.js"></script>
<script>

var format = d3.format(",.2f");

var tests = [
  {start:  170, end:  225, expected: [ 170.00, -176.25, -162.50, -148.75, -135.00]},
  {start:  225, end:  170, expected: [-135.00, -148.75, -162.50, -176.25,  170.00]},
  {start: -170, end: -225, expected: [-170.00,  176.25,  162.50,  148.75,  135.00]},
  {start: -225, end: -170, expected: [ 135.00,  148.75,  162.50,  176.25, -170.00]},
  {start: -170, end:  170, expected: [-170.00, -175.00,  180.00,  175.00,  170.00]},
  {start: -170, end:    0, expected: [-170.00, -127.50,  -85.00,  -42.50,    0.00]},
  {start:  170, end:    0, expected: [ 170.00,  127.50,   85.00,   42.50,    0.00]},
  {start: -180, end:   90, expected: [ 180.00,  157.50,  135.00,  112.50,   90.00]},
  {start:  180, end:   90, expected: [ 180.00,  157.50,  135.00,  112.50,   90.00]},
  {start: -180, end:  -90, expected: [-180.00, -157.50, -135.00, -112.50,  -90.00]},
  {start:  180, end:  -90, expected: [ 180.00, -157.50, -135.00, -112.50,  -90.00]},
  {start:  780, end:  -90, expected: [  60.00,  22.50,  -15.00,   -52.50,  -90.00]}
];

var tr = d3.select("tbody").selectAll("tr")
    .data(tests)
  .enter().append("tr");

tr.append("td")
    .text(function(d) { return format(d.start); });

tr.append("td")
    .text(function(d) { return format(d.end); });

tr.selectAll(".actual")
    .data(function(d) {
      var interpolate = d3.interpolateTransform("rotate(" + d.start + ")", "rotate(" + d.end + ")");
      return d.expected.map(function(expected, i) {
        return {
          expected: expected,
          actual: d3.transform(interpolate(i / 4)).rotate
        };
      });
    })
  .enter().append("td")
    .text(function(d, i) { return format(d.actual); })
    .attr("class", function(d) { return Math.abs(d.actual - d.expected) < .01 ? "success" : "fail"; });

var ga = tr.append("td").attr("width", 40).append("svg")
    .attr("width", 40)
    .attr("height", 20)
  .append("g")
    .attr("transform", "translate(20,10)")
  .append("g")
    .each(animateExpected);

ga.append("path")
    .attr("d", d3.svg.symbol().type("cross").size(120));

ga.append("circle")
    .attr("cx", 8)
    .attr("r", 4);

var gb = tr.append("td").attr("width", 40).append("svg")
    .attr("width", 40)
    .attr("height", 20)
  .append("g")
    .attr("transform", "translate(20,10)")
  .append("g")
    .each(animateActual);

gb.append("path")
    .attr("d", d3.svg.symbol().type("cross").size(120));

gb.append("circle")
    .attr("cx", 8)
    .attr("r", 4);

function animateExpected(d) {
  d3.select(this).transition()
      .duration(2500)
      .attrTween("transform", function(d) {
        var a = d.start % 360, b = d.end % 360;
        if (a - b > 180) b += 360; else if (b - a > 180) a += 360; // shortest path
        return d3.interpolateString("rotate(" + a + ")", "rotate(" + b + ")");
      })
      .each("end", animateExpected);
}

function animateActual(d) {
  d3.select(this)
      .attr("transform", "rotate(" + d.start + ")")
    .transition()
      .duration(2500)
      .attr("transform", "rotate(" + d.end + ")")
      .each("end", animateActual);
}

</script>
